﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using ZoneFiveSoftware.Common.Visuals;

#if DEBUG
using NUnit.Core;
#endif

namespace STCommon.NUnit
{
    public partial class NUnitRunnerControl : UserControl
#if DEBUG
        , EventListener
#endif
    {
        public NUnitRunnerControl()
        {
            InitializeComponent();

            m_FontFamilyMap = new HybridDictionary();
            m_FontFamilyMap.Add(FontFamily.GenericMonospace.Name, @"\fmodern");
            m_FontFamilyMap.Add(FontFamily.GenericSansSerif, @"\fswiss");
            m_FontFamilyMap.Add(FontFamily.GenericSerif, @"\froman");

#if DEBUG
            NUnitTestManager.Instance.TestAssemblyRegistered += new PropertyChangedEventHandler(OnTestAssemblyRegistered);
            OnTestAssemblyRegistered(this, null);
#endif
        }

#if DEBUG
        private delegate void OnTestAssemblyRegisteredDelegate(object sender, PropertyChangedEventArgs e);
        void OnTestAssemblyRegistered(object sender, PropertyChangedEventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new OnTestAssemblyRegisteredDelegate(OnTestAssemblyRegistered),
                       new object[] { sender, e });
            }
            else
            {
                TestSuitesComboBox.Items.Clear();

                foreach (String testAssembly in NUnitTestManager.Instance.TestAssemblies)
                {
                    TestSuitesComboBox.Items.Add(testAssembly);
                }

                if (TestSuitesComboBox.Items.Count > 0)
                {
                    TestSuitesComboBox.SelectedIndex = 0;
                }
            }
        }
#endif

        public void ThemeChanged(ITheme visualTheme)
        {
        }

        public void UICultureChanged(CultureInfo culture)
        {
        }

#if DEBUG
#region EventListener Members

        private delegate void RunFinishedExceptionDelegate(Exception exception);
        public void RunFinished(Exception exception)
        {
            if (InvokeRequired)
            {
                Invoke(new RunFinishedExceptionDelegate(RunFinished),
                       new object[] { exception });
            }
            else
            {
                using(Font tempFont = new Font(ResultsRichTextBox.Font, FontStyle.Bold))
                {
                    AppendRTFText(String.Format("\n===== Run completed with exception : {0} =====",
                                                exception.Message),
                                  Color.Black, tempFont);
                }

                if (!String.IsNullOrEmpty(m_SelectedTestSuite))
                {
                    TestSuitesComboBox.SelectedItem = m_SelectedTestSuite;
                }
            }
        }

        private delegate void RunFinishedDelegate(TestResult result);
        public void RunFinished(TestResult result)
        {
            if (InvokeRequired)
            {
                Invoke(new RunFinishedDelegate(RunFinished),
                       new object[] { result });
            }
            else
            {
                using (Font tempFont = new Font(ResultsRichTextBox.Font, FontStyle.Bold))
                {
                    AppendRTFText(String.Format("\n===== Run completed in {1} seconds with result : {0} =====",
                                                GetResultText(result), result.Time),
                                  GetResultColor(result), tempFont);
                }

                if (!String.IsNullOrEmpty(m_SelectedTestSuite))
                {
                    TestSuitesComboBox.SelectedItem = m_SelectedTestSuite;
                }
            }
        }

        private delegate void RunStartedDelegate(string name, int testCount);
        public void RunStarted(string name, int testCount)
        {
            if (InvokeRequired)
            {
                Invoke(new RunStartedDelegate(RunStarted),
                       new object[] { name, testCount });
            }
            else
            {
                m_SelectedTestSuite = TestSuitesComboBox.SelectedItem as String;

                ProgressBar.Maximum = testCount;
                ProgressBar.Value = 0;

                string clearLines = "";

                if (ResultsRichTextBox.TextLength > 0)
                {
                    clearLines = "\n\n";
                }

                using (Font tempFont = new Font(ResultsRichTextBox.Font.FontFamily, 12, FontStyle.Bold))
                {
                    AppendRTFText(String.Format("{1}===== Run started with {0} test(s) to run =====",
                                                testCount, clearLines),
                                  Color.Black, tempFont);
                }
            }
        }

        private delegate void SuiteFinishedDelegate(TestResult result);
        public void SuiteFinished(TestResult result)
        {
            if (InvokeRequired)
            {
                Invoke(new SuiteFinishedDelegate(SuiteFinished),
                       new object[] { result });
            }
            else
            {
                using (Font tempFont = new Font(ResultsRichTextBox.Font.FontFamily, 11, FontStyle.Regular))
                {
                    AppendRTFText(String.Format("\n***** Suite {0} completed with result : {1} *****",
                                                result.Name, GetResultText(result)),
                                  GetResultColor(result), tempFont);
                }
            }
        }

        private delegate void SuiteStartedDelegate(TestName testName);
        public void SuiteStarted(TestName testName)
        {
            if (InvokeRequired)
            {
                Invoke(new SuiteStartedDelegate(SuiteStarted),
                       new object[] { testName });
            }
            else
            {
                using (Font tempFont = new Font(ResultsRichTextBox.Font.FontFamily, 11, FontStyle.Regular))
                {
                    AppendRTFText(String.Format("\n----- Suite {0} started -----",
                                                testName.Name),
                                  Color.Black, tempFont);
                }
            }
        }

        private delegate void TestFinishedDelegate(TestResult result);
        public void TestFinished(TestResult result)
        {
            if (InvokeRequired)
            {
                Invoke(new TestFinishedDelegate(TestFinished),
                        new object[] { result });
            }
            else
            {
                ProgressBar.PerformStep();

                AppendRTFText(String.Format("\nTest {0} completed with result : {1}", result.Name, GetResultText(result)),
                              GetResultColor(result), ResultsRichTextBox.Font);
            }
        }

        private delegate void TestOutputDelegate(TestOutput testOutput);
        public void TestOutput(TestOutput testOutput)
        {
            if (InvokeRequired)
            {
                Invoke(new TestOutputDelegate(TestOutput),
                       new object[] { testOutput });
            }
            else
            {
                AppendRTFText("\n" + testOutput.Text,
                              Color.Black, ResultsRichTextBox.Font);
            }
        }

        private delegate void TestStartedDelegate(TestName testName);
        public void TestStarted(TestName testName)
        {
            if (InvokeRequired)
            {
                Invoke(new TestStartedDelegate(TestStarted),
                       new object[] { testName });
            }
            else
            {
                AppendRTFText(String.Format("\nTest {0} started", testName.Name),
                              Color.Black, ResultsRichTextBox.Font);
            }
        }

        private delegate void UnhandledExceptionDelegate(Exception exception);
        public void UnhandledException(Exception exception)
        {
            if (InvokeRequired)
            {
                Invoke(new UnhandledExceptionDelegate(UnhandledException),
                       new object[] { exception });
            }
            else
            {
                AppendRTFText(String.Format("Exception occurred : {0}", exception.Message),
                              Color.Black, ResultsRichTextBox.Font);
            }
        }

#endregion
#endif

#if DEBUG
        private void RunAllTestsButton_Click(object sender, EventArgs e)
        {
            NUnitTestManager.Instance.RunTests(this);
        }

        private void RunTestsButton_Click(object sender, EventArgs e)
        {
            if (TestSuitesComboBox.SelectedItem != null)
            {
                NUnitTestManager.Instance.RunTests(TestSuitesComboBox.SelectedItem as string, this);
            }
            else
            {
                NUnitTestManager.Instance.RunTests(this);
            }
        }
#else
        private void RunAllTestsButton_Click(object sender, EventArgs e)
        {
        }

        private void RunTestsButton_Click(object sender, EventArgs e)
        {
        }
#endif

        private void ClearResultsButton_Click(object sender, EventArgs e)
        {
            ResultsRichTextBox.Rtf = "";
        }

        private void TestSuitesComboBox_Format(object sender, ListControlConvertEventArgs e)
        {
            // Strip path and keep only assembly name
            string assemblyPath = e.ListItem as string;
            string assemblyName = assemblyPath.Substring(assemblyPath.LastIndexOf('\\') + 1);

            assemblyName = assemblyName.Substring(0, assemblyName.LastIndexOf('.'));

            e.Value = assemblyName;
        }

        private void AppendRTFText(string text, Color color, Font font)
        {
            StringBuilder rtfString = new StringBuilder();

            rtfString.Append(m_RTFHeader);
            rtfString.Append(GetFontTable(font));
            rtfString.Append(GetColorTable(color));
            rtfString.Append(GetDocumentArea(text, font));

            ResultsRichTextBox.Select(ResultsRichTextBox.TextLength, 0);
            ResultsRichTextBox.SelectedRtf = rtfString.ToString();

            ResultsRichTextBox.ScrollToCaret();
        }


        /// <summary>
        /// Creates a font table from a font object.  When an Insert or Append 
        /// operation is performed a font is either specified or the default font
        /// is used.  In any case, on any Insert or Append, only one font is used,
        /// thus the font table will always contain a single font.  The font table
        /// should have the form ...
        /// 
        /// {\fonttbl{\f0\[FAMILY]\fcharset0 [FONT_NAME];}
        /// 
        /// This code was provided by Khendys Gordon and is available on Code Project :
        /// http://www.codeproject.com/KB/edit/csexrichtextbox.aspx
        /// </summary>
        /// <param name="_font"></param>
        /// <returns></returns>
        private string GetFontTable(Font _font)
        {
            StringBuilder fontTable = new StringBuilder();

            // Append table control string
            fontTable.Append(@"{\fonttbl{\f0");
            fontTable.Append(@"\");

            // If the font's family corresponds to an RTF family, append the
            // RTF family name, else, append the RTF for unknown font family.
            if (m_FontFamilyMap.Contains(_font.FontFamily.Name))
            {
                fontTable.Append(m_FontFamilyMap[_font.FontFamily.Name]);
            }
            else
            {
                fontTable.Append(@"\fnil");
            }

            // \fcharset specifies the character set of a font in the font table.
            // 0 is for ANSI.
            fontTable.Append(@"\fcharset0 ");

            // Append the name of the font
            fontTable.Append(_font.Name);

            // Close control string
            fontTable.Append(@";}}");

            return fontTable.ToString();
        }

        /// <summary>
        /// Creates a font table from the RtfColor structure.  When an Insert or Append
        /// operation is performed, _textColor and _backColor are either specified
        /// or the default is used.  In any case, on any Insert or Append, only three
        /// colors are used.  The default color of the RichTextBox (signified by a
        /// semicolon (;) without a definition), is always the first color (index 0) in
        /// the color table.  The second color is always the text color, and the third
        /// is always the highlight color (color behind the text).  The color table
        /// should have the form ...
        /// 
        /// {\colortbl ;[TEXT_COLOR];[HIGHLIGHT_COLOR];}
        /// 
        /// This code was provided by Khendys Gordon and is available on Code Project :
        /// http://www.codeproject.com/KB/edit/csexrichtextbox.aspx
        /// </summary>
        /// <param name="_textColor"></param>
        /// <param name="_backColor"></param>
        /// <returns></returns>
        private string GetColorTable(Color color)
        {
            StringBuilder colorTable = new StringBuilder();

            // Append color table control string and default font (;)
            colorTable.Append(@"{\colortbl ;");

            // Append the text color
            colorTable.Append(String.Format(@"\red{0}\green{1}\blue{2}", color.R, color.G, color.B));
            colorTable.Append(@";}");

            return colorTable.ToString();
        }

        /// <summary>
        /// Creates the Document Area of the RTF being inserted. The document area
        /// (in this case) consists of the text being added as RTF and all the
        /// formatting specified in the Font object passed in. This should have the
        /// form ...
        /// 
        /// \viewkind4\uc1\pard\cf1\f0\fs20 [DOCUMENT AREA] }
        ///
        /// This code was provided by Khendys Gordon and is available on Code Project :
        /// http://www.codeproject.com/KB/edit/csexrichtextbox.aspx
        /// </summary>
        /// <param name="_text"></param>
        /// <param name="_font"></param>
        /// <returns>
        /// The document area as a string.
        /// </returns>
        private string GetDocumentArea(string text, Font font)
        {
            StringBuilder doc = new StringBuilder();

            // Append the standard RTF document area control string
            doc.Append(m_RTFDocumentPrefix);

            // Set the highlight color (the color behind the text) to the
            // third color in the color table.  See GetColorTable for more details.
            doc.Append(@"\highlight2");

            // If the font is bold, attach corresponding tag
            if (font.Bold)
                doc.Append(@"\b");

            // If the font is italic, attach corresponding tag
            if (font.Italic)
                doc.Append(@"\i");

            // If the font is strikeout, attach corresponding tag
            if (font.Strikeout)
                doc.Append(@"\strike");

            // If the font is underlined, attach corresponding tag
            if (font.Underline)
                doc.Append(@"\ul");

            // Set the font to the first font in the font table.
            // See GetFontTable for more details.
            doc.Append(@"\f0");

            // Set the size of the font.  In RTF, font size is measured in
            // half-points, so the font size is twice the value obtained from
            // Font.SizeInPoints
            doc.Append(@"\fs");
            doc.Append((int)Math.Round((2 * font.SizeInPoints)));

            // Apppend a space before starting actual text (for clarity)
            doc.Append(@" ");

            // Append actual text, however, replace newlines with RTF \par.
            // Any other special text should be handled here (e.g.) tabs, etc.
            doc.Append(text.Replace("\\", @"\\"));
            doc.Replace("\n", @"\par ");

            // RTF isn't strict when it comes to closing control words, but what the
            // heck ...

            // Remove the highlight
            doc.Append(@"\highlight0");

            // If font is bold, close tag
            if (font.Bold)
                doc.Append(@"\b0");

            // If font is italic, close tag
            if (font.Italic)
                doc.Append(@"\i0");

            // If font is strikeout, close tag
            if (font.Strikeout)
                doc.Append(@"\strike0");

            // If font is underlined, cloes tag
            if (font.Underline)
                doc.Append(@"\ulnone");

            // Revert back to default font and size
            doc.Append(@"\f0");
            doc.Append(@"\fs20");

            // Close the document area control string
            doc.Append(m_RTFDocumentPostfix);

            return doc.ToString();
        }

#if DEBUG
        private string GetResultText(TestResult result)
        {
            string resultMessage = "";

            switch(result.ResultState)
            {
                case ResultState.Cancelled:
                    {
                        resultMessage += "Cancelled";
                        break;
                    }
                case ResultState.Error:
                    {
                        resultMessage += "Error";
                        resultMessage += "\n" + result.Message;
                        break;
                    }
                case ResultState.Failure:
                    {
                        resultMessage += "Failure";
                        resultMessage += "\n" + result.Message;
                        break;
                    }
                case ResultState.Ignored:
                    {
                        resultMessage += "Ignored";
                        break;
                    }
                case ResultState.Inconclusive:
                    {
                        resultMessage += "Inconclusive";
                        break;
                    }
                case ResultState.NotRunnable:
                    {
                        resultMessage += "NotRunnable";
                        break;
                    }
                case ResultState.Skipped:
                    {
                        resultMessage += "Skipped";
                        break;
                    }
                case ResultState.Success:
                    {
                        resultMessage += "Success";
                        break;
                    }
            }

            return resultMessage;
        }

        private Color GetResultColor(TestResult result)
        {
            switch (result.ResultState)
            {
                case ResultState.Error:
                    {
                        return Color.Red;
                    }
                case ResultState.Failure:
                    {
                        return Color.Red;
                    }
                case ResultState.Ignored:
                    {
                        return Color.Yellow;
                    }
                case ResultState.Inconclusive:
                    {
                        return Color.DarkGray;
                    }
                case ResultState.Success:
                    {
                        return Color.Green;
                    }
            }

            return Color.Black;
        }
#endif

        /// This code was provided by Khendys Gordon and is available on Code Project :
        /// http://www.codeproject.com/KB/edit/csexrichtextbox.aspx
        private const string m_RTFDocumentPrefix = @"\viewkind4\uc1\pard\cf1\f0\fs20";
        private const string m_RTFDocumentPostfix = @"\cf0\fs17}";
        private const string m_RTFHeader = @"{\rtf1\ansi\ansicpg1252\deff0\deflang1033";
        private HybridDictionary m_FontFamilyMap = null;

        private string m_SelectedTestSuite = String.Empty;
    }
}
